package network;

import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;

import encryption.EncryptionPlugin;

/**
 * This class is designed to connect to a 'server' and
 *  then request the address of redundant servers in case of failure.
 * This will also allow others to connect to it, and relay messages.
 * It will also provide a redundant server for those that use it as a server.
 * 
 * If there are the max # of connections the user wants to allow, give connecter 'redund' and disconnect.
 * 
 * 
 * This class has to run in 2 separate threads, a server for incoming connections, and client for out going.
 * +gui and other stuff.
 * 
 * @author jex
 *
 */
public class Peer {

	public static void main(String[] args){
		System.out.println("Testing");
		Thread.dumpStack();
	}

	public static final byte BROADCAST_F = 1;//allowed to broadcast
	public static final byte SERVER_F = 2;//allowed to have incoming connections
	//this is so that connections to the root server won't allow their friends to connect through them.
	public static final byte something = 4;//some other flag. not used.

	public EncryptionPlugin encr;
	protected ServerList redundc;//connect to if one of our clients disconnects.
	protected ServerList redund;//for our clients to connect to if we disconnect
	protected ServerList redundS;//connect to if 'serv' disconnects
	//both redundant lists are formated: <ip><port>
	
	private Client client;
	
	private byte flags = 0;//set by our server, who decides some things.
	//This needs to be passed without modification to our clients,
	//to ensure that everyone respects the 'rules' laid out by the root server.

	private ArrayList<Connection> clients = new ArrayList<Connection>();//TODO allow user to select max size
	private ArrayList<ServerThread> threads = new ArrayList<ServerThread>();
	private volatile boolean servRunning = false;
	
	private DisconnectThread dthread = null;//
	
	public Peer(EncryptionPlugin e){
		encr = e;
		redundc = new ServerList(NetworkVars.inetLen);
		redund = new ServerList(NetworkVars.inetLen);
		redundS = new ServerList(NetworkVars.inetLen);
		dthread = new DisconnectThread(this);
		Thread t = new Thread(dthread);
		t.start();
	}

	//-------All methods are named such that Client/Server is what others would see us as-------


	/**
	 * Start a server listening on 'port' and allow a maximum of numCli connections.
	 * If the user has chosen to broadcast, then only broadcast if there is at least 1 open connection spot.
	 * @param numCli Maximum number of connections to allow. (min 1)
	 * @param port Port to listen on.
	 */
	/*
	 * Uses 'servRunning' to show that server is running, to close server 'servRunning' is set to false
	 * 
	 */
	public void run_Server(int numCli, int port){
		if(servRunning){
			return;
		}
		servRunning = true;
		if(numCli < 1){
			numCli = 1;
		}
		clients.ensureCapacity(numCli);

		try {
			ServerSocket socket = new ServerSocket(port);

			while(servRunning){

				Connection c = new Connection(encr);
				c.setSocket( socket.accept() );
				
				if( !NetIO.connect(c) ){
					//didn't connect, drop em.
					c = null;
					continue;
				}
				
				if( clients.size() > numCli ){
					//connect, send them redund list, and exit.
					//use tempthread to do this.
					
					dthread.addConnection(c);
					
					//TODO: deal with gap between number of connections + number allowed
					/* It would probably be safe to assume that we are being DDoS ed if
					   the gap between the two grows too large. At that point we should
					   just start disconnecting connections when we get them */
					
				} else {
					//we can actually add this person. Add them.
					int conStat = 0;
					for(ServerThread st : threads){
						conStat = st.addConnection(c);
						if( conStat < 1){
							break;//connected or not able to connect. 
						}
					}
					if( conStat > 0 ){
						//didn't get added into any thread.
						ServerThread st = new ServerThread(this);
						threads.add(st);
						conStat = st.addConnection(c);//this will be able to add it.
						//but may not be able to connect.
					} 
					if( conStat == 0 ){
						//got added, add to list of clients
						clients.add(c);
					}
				}
			}//end of loop

		} catch (IOException e) {
			System.out.println("Couldn't create server: "+e.getMessage());
		}

		servRunning = false;
	}
	/**
	 * Send the message 'm' to all connections, except the one it came from.
	 * If message.orig is null, then it came from this client, and will
	 * get sent to everyone.
	 * @param m message to send
	 */
	public void send(Message m){
		for (Connection c : clients) {
			if (m.orig == c || c == null) {
				continue;
			}
			c.addMsg(m);
		}
		if(client != null && client.serv != null && m.orig != client.serv){
			client.serv.addMsg(m);
		}
	}

	public void send(Message m, Connection c){
		if(m.orig != c){
			c.addMsg(m);
		}
	}


	/**
	 * This method gives our clients a list of redundant servers for use after we disconnect.
	 * Then disconnects from them.
	 * This is called after breaking from the regular loop, so all messages must be written, and not added to the queue.
	 */
	public void disconnect_Serv(){
		Message m = new Message(Message.INFO,core.Util.flatten(redund.getList())); 
		for(Connection c : clients){
			try {
				NetIO.write(c,m);
			} catch (DisconnectionException e) {
				//this is fine: already disconnected.
				System.err.println("Couldn't send "+c.user.getIp().getHostAddress()+" redundant list.");
				continue;//go to next connection
			}
			//we've sent the redundant list, now disconnect
			try {
				NetIO.disconnect(c);
			} catch (DisconnectionException e) {
				//already done. don't do anything.
			} catch (IOException e) {
				//more of a problem. Print stack trace, not sure how to handle.
				System.err.println("Couldn't disconnect properly from: "+c.user.getIp().getHostAddress()+" after sending redundant list.");
				e.printStackTrace();
			}
		}
	}
	
	

	public long getStartTime(){
		return client.getStartTime();
	}

	public String[] getRedund(){
		return redund.getList().clone();
	}


	public String[] getRedundC(){
		return redundc.getList().clone();
	}

	public boolean canBroadcast(){
		return (flags & BROADCAST_F)> 0;
	}

	public User getServer(){
		return client.serv.user;
	}

	public Connection getUser(String str){
		for( Connection c : clients){
			if(c.user.get().contains(str)){
				return c;
			}
		}
		return null;
	}

	public void setFlags(byte b){
		flags = b;
	}
	
}





